/*
 * pointrank.cpp
 *
 *  Created on: May 6, 2014
 *      Author: killerg
 */

#include "../server_deps.h"
#include "../server.h"
#include "../server_cfg.h"

#include "player_data.h"
#include "pointrank.h"
#include "mail_mgr.h"

#include "share/db_mgr.h"
#include "share/clock.h"

static time_t _get_finish(time_t tt) {
	tm now = *localtime(&tt);

//	if ((now.tm_wday == 3 && now.tm_hour < 12) || now.tm_wday < 3) {
//		now.tm_hour = 12;
//		now.tm_mday += 3-now.tm_wday;
//	} else {
//		now.tm_hour = 0;
//		now.tm_mday += 7-now.tm_wday;
//	}

	now.tm_mday += 4;
	now.tm_hour = 0;
	now.tm_min = 0;
	now.tm_sec = 0;

	return mktime(&now);
}

pointrank_t* pointrank_create() {
	pointrank_t* pr = new pointrank_t;
	return pr;
}

void pointrank_delete(pointrank_t* pr) {
	delete pr;
}

void pointrank_init(pointrank_t* pr, const Json::Value &json) {
	pr->groups.clear();

	pr->finish = json["finish"].asInt();

	const Json::Value &jfamilies = json["families"];
	for (int i=0; i<jfamilies.size(); ++i) {
		pr->pet_family.insert(jfamilies.get(i, Json::nullValue).asInt());
	}

	const Json::Value &jgroups = json["groups"];
	for (int i=0; i<jgroups.size(); ++i) {
		const Json::Value &jgroup = jgroups.get(i, Json::nullValue);
		const Json::Value &jmembers = jgroup["members"];
		pr->groups.push_back(pointrank_t::group_t());
		auto &group = pr->groups.back();
		group.last_win = jgroup["lw"].asCString();
		for (int ii=0; ii<jmembers.size(); ++ii) {
			const Json::Value &jmember = jmembers.get(ii, Json::nullValue);

			pointrank_t::member_t mem;
			mem.id = jmember["id"].asInt64();
			mem.points = jmember["pts"].asInt();
			mem.win_con = jmember["wc"].asInt();
			if (jmember["stub"].isObject()) {
				mem.stub = make_shared<player_t>();
				mem.stub->init(jmember["stub"]);
			}
			group.members.push_back(mem);
		}
	}
}

void pointrank_save(pointrank_t* pr, Json::Value &json) {
	json["finish"] = pr->finish;
	for (auto &itr : pr->pet_family) {
		json["families"].append(itr);
	}

	Json::Value &jgroups = json["groups"];
	for (auto &group : pr->groups) {
		Json::Value jgroup;
		Json::Value jmembers;

		for (auto &member : group.members) {
			Json::Value jmember;
			jmember["id"] = Json::Int64(member.id);
			jmember["pts"] = Json::Int64(member.points);
			jmember["wc"] = member.win_con;
			if (member.stub)
				member.stub->save(jmember["stub"]);

			jmembers.append(jmember);
		}
		jgroup["members"] = jmembers;
		jgroup["lw"] = group.last_win;
		jgroups.append(jgroup);
	}
}

void pointrank_print(pointrank_t* pr) {
	printf("groups:%d\n", pr->groups.size());
	for (auto &itr : pr->groups) {
		printf("\tgroup:%d\n", itr.members.size());
	}

}

int pointrank_get_member(pointrank_t* pr, int64_t playerid, pointrank_t::member_t* &mem, int &group) {
	for (int ii=0; ii<pr->groups.size(); ++ii) {
		for (int i=0; i<pr->groups[ii].members.size(); ++i) {
			if (pr->groups[ii].members[i].id == playerid) {
				mem = &pr->groups[ii].members[i];
				group = ii;
				return i;
			}
		}
	}
	return -1;
}

bool pointrank_checkpets(pointrank_t* pr, sp_player_t player) {
	for (int i=0; i<3; ++i) {
		auto pet = player->pets.get(player->combat[i]);
		if (!pet)
			continue;

		int nproto = pet->att(Pe4s_Proto);
		const auto *proto = MAProto(Pet, nproto);
		if (!proto)
			continue;

		if (!pr->pet_family.count(proto->family))
			return false;
	}

	return true;
}

int pointrank_get_member_num(pointrank_t* pr, int group) {
	int num = 0;
	if (group < 0) {
		for (auto &itr : pr->groups)
			num += itr.members.size();
	} else if (group >= pr->groups.size()) {
		num = pr->groups[group].members.size();
	}

	return num;
}

static bool _post_save(pointrank_t* pr, bool block) {
	if (!g_dbf->ok())
		return false;

	Json::Value json;
	pointrank_save(pr, json);
	bool ret = g_dbs->set(
			fmt_cstr("_point_rank:%d", g_cfg->ServerId),
			json,
			[pr, &ret, block](db_error_t err){
				if (err != DBE_Success) {
					UTIL_LOG(E, "_point_rank save err");
					if (block)
						ret = false;
				} else {
					if (block)
						ret = true;
				}
			});

	if (!ret) {
		UTIL_LOG(E, "_point_rank save err");
		return false;
	}

	if (block)
		g_dbs->wait();

	return ret;
}

bool pointrank_init(pointrank_t* pr) {
	pr->groups.clear();
	pr->pet_family.clear();
	pr->finish = 0;

    bool ret = g_dbf->get(
            fmt_cstr("_point_rank:%d", g_cfg->ServerId),
            [&ret, pr](db_error_t err, const Json::Value &value){
                switch (err) {
                case DBE_Success: {
                	pointrank_init(pr, value);
                	ret = true;
                    break;
                }
                case DBE_NoExist:
                    UTIL_LOG(I, "no _point_rank data");
                    ret = true;
                    break;
                default:
                    UTIL_LOG(E,
                            "load _point_rank data err: %s",
                            db_strerror(err));
                    ret = false;
                    break;
                }
            });
    if (!ret) {
        UTIL_LOG(E, "pointrank_init get _point from db failed");
        return false;
    }

    g_dbf->wait();

	if (pr->finish <= 0) {
		pointrank_reset(pr);
		pr->change_count = 100;
	}

    ret = g_dbf->get(
            fmt_cstr("_point_reward:%d", g_cfg->ServerId),
            [&ret, pr](db_error_t err, const Json::Value &value){
                switch (err) {
                case DBE_Success: {
                	pr->rewarding_group = value["group"].asInt();
                	pr->rewarding_index = value["index"].asInt();
                	ret = true;
                    break;
                }
                case DBE_NoExist:
                	pr->rewarding_group = -1;
                	pr->rewarding_index = -1;
                    UTIL_LOG(I, "no _point_rank data");
                    ret = true;
                    break;
                default:
                    UTIL_LOG(E,
                            "load _point_reward data err: %s",
                            db_strerror(err));
                    ret = false;
                    break;
                }
            });
    if (!ret) {
        UTIL_LOG(E, "pointrank_init get _point reward from db failed");
        return false;
    }

    g_dbf->wait();

	return true;
}

void pointrank_destroy(pointrank_t* pr) {
	for (int i=0; i<10; ++i) {
		if (_post_save(pr, true))
			break;
		UTIL_LOG(E, "try %d times to save pointrank", i+1);
		sleep(1);
	}

	pr->finish = -1;
	pr->groups.clear();
	pr->pet_family.clear();

	pr->change_count = 0;
}

static int32_t _find_rewardid(int ngroup, int points, int &range) {
	range = -1;
	const auto* proto = ARProto(Pointrank, ngroup);
	if (!proto)
		return -1;

	int i=0;
	for (auto &itr : proto->rewards) {
		if (points >= std::get<0>(itr) && points <= std::get<1>(itr)) {
			range = i;
			return std::get<2>(itr);
		}
		++i;
	}
	return -1;
}

static void _sort_group(pointrank_t* pr, bool force) {
	for (auto &group : pr->groups) {
		if (!force && group.update_count < 1)
			continue;

		group.update_count = 0;
		std::sort(
				group.members.begin(),
				group.members.end(),
				[](const pointrank_t::member_t &lhs, const pointrank_t::member_t &rhs){
					return lhs.points > rhs.points;
				});
	}
}

void pointrank_update(pointrank_t* pr) {
	if (g_clock->tt_now > pr->finish) {
		if (pr->rewarding_group < 0 || pr->rewarding_index < 0) {
			_sort_group(pr, true);

			pr->rewarding_group = 0;
			pr->rewarding_index = 0;
		} else {
			if (pr->rewarding_group >= pr->groups.size()) {
				pr->rewarding_group = -1;
				pr->rewarding_index = -1;

				pointrank_reset(pr);
				pr->change_count = 100;
			} else if (pr->rewarding_index >= pr->groups[pr->rewarding_group].members.size()) {
				++pr->rewarding_group;
				pr->rewarding_index = 0;
			} else {
				int range = -1;
				auto mem = pr->groups[pr->rewarding_group].members[pr->rewarding_index];
				int32_t rewardid = _find_rewardid(pr->rewarding_group, mem.points, range);
				if (rewardid >= 0) {
					string str = fmt_cstr("%d", range);
					g_mail->send_system(
							mem.id,
							EMT_PointRankReward,
							str.c_str(),
							rewardid,
							1.0f,
							[mem, rewardid](bool ok){
								if (!ok) {
									auto * info = g_playermgr->infos.get(mem.id);
									UTIL_LOG(E, "pointrank reward player %lld name %s reward %d err", mem.id, info?info->name.c_str():"", rewardid);
								}
							});
				}

				++pr->rewarding_index;
			}
		}

		Json::Value jreward;
		jreward["group"] = pr->rewarding_group;
		jreward["index"] = pr->rewarding_index;
		bool ret = g_dbf->set(
				fmt_cstr("_point_reward:%d", g_cfg->ServerId),
				jreward,
				[pr](db_error_t err){
					if (err != DBE_Success)
						UTIL_LOG(E, "_point_reward save err %d %d", pr->rewarding_group, pr->rewarding_index);
				});
		if (!ret) {
			UTIL_LOG(E, "_point_reward save err %d %d", pr->rewarding_group, pr->rewarding_index);
		}

	}

	static double timer = 60 * 10;
	timer -= g_clock->delta;
	if (timer < 0 || pr->change_count >= g_sessmgr->connmgr.size() + 1) {
		if (g_clock->tt_now <= pr->finish)
			_sort_group(pr, false);

		if (_post_save(pr, false)) {
			pr->change_count = 0;
			timer = 60 * 10;
		}
	}
}

void pointrank_reset(pointrank_t* pr) {
	pr->finish = _get_finish(time(NULL));

	for (auto &itr : pr->groups) {
		if (!itr.members.empty())
			itr.last_win = itr.members.front().stub->att(Plss_Name);

		itr.members.clear();
	}

	pr->pet_family.clear();
	for (int i=0; i<6 && pr->pet_family.size() < 2; ++i)
		pr->pet_family.insert(rand() % EPetProperty_ARRAYSIZE);
}
//
//struct test_player_t {
//	int64_t id;
//	int medal;
//	int group;
//};
//static pointrank_t* s_pr;
//
//static void handle_pointrankcombat(test_player_t* player) {
//	int cur_group = player->group;
//	if (player->group < 0)
//		player->group = player->medal;
//
//	while (s_pr->groups.size() <= player->group)
//		s_pr->groups.push_back(pointrank_t::group_t());
//
//	auto &group = s_pr->groups[player->group];
//	if (cur_group < 0) {
//		pointrank_t::member_t mem;
//		mem.id = player->id;
//		mem.points = 0;
//		mem.win_con = 0;
//		group.members.push_back(mem);
//	}
//
//	int32_t idx = -1;
//	for (int i=0; i<group.members.size() && idx<0; ++i)
//		if (group.members[i].id == player->id)
//			idx = i;
//	if (idx < 0)
//		return;
//
//	int32_t other_idx = cut<int32_t>(idx+rand()%6-3, 0, group.members.size()-1);
//	assert(other_idx >= 0 && other_idx < group.members.size());
//
//	// combat
//	if (rand() % 2) {
//		group.members[idx].points += 100;
//		group.members[other_idx].points += 10;
//
//		++group.members[idx].win_con;
//	} else {
//		group.members[idx].points += 10;
//		group.members[other_idx].points += 100;
//
//		group.members[idx].win_con = 0;
//	}
//	// sort
//	static int counter = 0;
//	if (counter++ % 100 == 0) {
//		std::sort(
//				group.members.begin(),
//				group.members.end(),
//				[](const pointrank_t::member_t &lhs, const pointrank_t::member_t &rhs){
//					return lhs.points > rhs.points;
//				});
//	}
//}
//
//bool pointrank_test() {
//	vector<test_player_t*> players;
//	for (int i=0; i<10000; ++i) {
//		auto player = new test_player_t;
//		player->id = 1000000000 + i;
//		player->medal = rand() % 6;
//		player->group = -1;
//		players.push_back(player);
//	}
//
//	s_pr = pointrank_create();
//
//	for (int i=0; i<10000; ++i) {
//		int rm = rand() % players.size();
//		handle_pointrankcombat(players[rm]);
//		if (i%10 == 0) {
//			pointrank_print(s_pr);
//		}
//	}
//
//	Json::Value save;
//	pointrank_save(s_pr,save);
//
//	pointrank_delete(s_pr);
//	s_pr = NULL;
//
//	for (auto &itr : players) {
//		delete itr;
//	}
//
//	save_file("pointsrank", to_cstr(save));
//
//	pointrank_t spr;
//	pointrank_init(&spr, save);
//
//	Json::Value tmp;
//	pointrank_save(&spr, tmp);
//
//	assert(save.toStyledString() == tmp.toStyledString());
//
//
//
//	return true;
//}
